package tictactoe;

import java.util.HashMap;
import java.util.Map;
import java.util.Stack;

import Framework.Best;
import Framework.Intelligence;

import nl.hanze.t23i.gamemodule.extern.AbstractGameModule;

/**
 * TicTacToe game
 */
public class GameIntelligence extends Intelligence
{
	public static final String GAME_TYPE = "TicTacToe";
	
    private int position = UNCLEAR;
    private char computerChar, humanChar;
    private Map<int[][], Integer> positions = new HashMap<int[][], Integer>();

    // Constructor
    public GameIntelligence(int rows, int columns, String PlayerOne, String PlayerTwo, int side)
    {
    	super(rows, columns, PlayerOne, PlayerTwo, side);
        clearBoard();
        initSide();
    }
    
    private void initSide()
    {
        if (side == COMPUTER)
        {
            computerChar = 'X';
            humanChar = 'O';
        }
        else
        {
            computerChar = 'O';
            humanChar = 'X';
        }
    }

    public void setComputerPlays()
    {
        this.side = COMPUTER;
        initSide();
    }

    public void setHumanPlays()
    {
        this.side = HUMAN;
        initSide();
    }

    public boolean computerPlays()
    {
        return side == COMPUTER;
    }

    /**
     * This method will return the best move
     *
     * @return
     */
    public int chooseMove()
    {
        positions.clear();
        Best best = chooseMove(COMPUTER, 0, HUMAN_WIN, COMPUTER_WIN);
        return best.row * 3 + best.column;
    }

    /**
     * Chooses the best move made by the
     *
     * @param side
     * @param amountCheckedBestMoves
     * @param human
     * @param computer
     * @return the best move decided by game theory.
     */
    public Best chooseMove(int side, int amountCheckedBestMoves, int human, int computer)
    {
        int opp; // The other side
        Best reply; // Opponent's best reply
        int simpleEval; // Result of an immediate evaluation
        int bestRow = 0;
        int bestColumn = 0;
        int value;

        if ((simpleEval = positionValue()) != UNCLEAR)
        {
            return new Best(simpleEval);
        }

        int[][] tmpBoard = board.clone();
        if (amountCheckedBestMoves >= 3 && amountCheckedBestMoves <= 5)
        {
            Integer pos = positions.get(tmpBoard);
            if (pos != null)
            {
                return new Best(pos);
            }
        }

        if (side == COMPUTER)
        {
            opp = HUMAN;
            value = human;
        }
        else
        {
            opp = COMPUTER;
            value = computer;
        }

        for (int i = 0; i < (board.length * board[0].length); i++)
        {
            int row = i / board.length;
            int column = i % board[0].length;

            // First check if the row, column combination is empty
            // If there is already a side on the board.. there's no need to check if it is the best move
            if (squareIsEmpty(row, column))
            {
                // After that place the current side on the board
                place(row, column, side);
                // Check if this is the best move or not
                reply = chooseMove(opp, amountCheckedBestMoves + 1, human, computer);
                // Remove the current side from the board again
                // So we can check whether this was the best move or not below
                place(row, column, EMPTY);

                // Is the current side a human? and was this the best move for the human?
                // Is the current side a computer? and was this the best move for the computer?
                if (side == HUMAN && reply.value < value || side == COMPUTER && reply.value > value)
                {
                    // Update the values of the opposite side
                    if (side == HUMAN)
                    {
                        computer = reply.value;
                        value = computer;
                    }
                    else
                    {
                        human = reply.value;
                        value = human;
                    }
                    bestRow = row;
                    bestColumn = column;
                    if (human >= computer)
                    {
                        break;
                    }
                }
            }
        }

        if (amountCheckedBestMoves <= 5)
        {
            positions.put(tmpBoard, value);
        }
        return new Best(value, bestRow, bestColumn);
    }

    /**
     * check if a move is fine to do.
     */
    public boolean moveOk(int move)
    {
        return (move >= 0 && move <= 8 && board[move / 3][move % 3] == EMPTY);
    }

    /**
     * play a move.
     */
    public void playMove(int move)
    {
        place((move / 3), (move % 3), this.side);
        this.side = side == COMPUTER ? HUMAN : COMPUTER;
    }

    /**
     * Clears the board if a new game begins.
     */
    private void clearBoard()
    {
        for (int i = 0; i < board.length; i++)
        {
            for (int j = 0; j < board[i].length; j++)
            {
                // for the number 2 everything is empty.
                board[i][j] = EMPTY;
            }
        }
    }

    /**
     * Checks all squares of the board whether its clear or filled. if one square is empty its not full.
     *
     * @return if the board is full yes or no
     */
    private boolean boardIsFull()
    {
        boolean full = true;
        for (int i = 0; i < board.length && full; i++)
            for (int j = 0; j < board[i].length && full; j++)
                if (board[i][j] == EMPTY) full = false;
        return full;
    }

    /**
     * Checks if a 'side' has won in this position.
     *
     * @param side
     * @return whether a side has won in this situation.
     */
    public boolean isAWin(int side)
    {
        /*
         * Check if one of the rows contains 3 the same side
         */
        for (int i = 0; i < board.length; i++)
        {
            int j;
            for (j = 0; j < board[i].length; j++)
                if (board[i][j] != side) break;
            if (j >= board[i].length) return true;
        }

        /*
         * Check if one of the columns contains 3 the same side
         */
        for (int j = 0; j < board[0].length; j++)
        {
            int i;
            for (i = 0; i < board.length; i++)
                if (board[i][j] != side) break;
            if (i >= board[1].length) return true;
        }

        /*
         * Don't forget to check the diagonals for a winning side
         */
        return (board[0][2] == side && board[1][1] == side && board[2][0] == side)
                || (board[1][1] == side && board[2][2] == side && board[0][0] == side);
    }

    /**
     * Play a move, possibly clearing a square
     *
     * @param row
     * @param column
     * @param piece
     */
    public void place(int row, int column, int piece)
    {
        board[row][column] = piece;
    }

    private boolean squareIsEmpty(int row, int column)
    {
        if (board[row][column] != HUMAN && board[row][column] != COMPUTER)
        {
            return board[row][column] == EMPTY;
        }
        return false;
    }

    /**
     * Compute static value of current position (win, draw, etc.)
     *
     * @return if its clear or unclear
     */
    public int positionValue()
    {
        if (isAWin(COMPUTER))
        {
            return COMPUTER_WIN;
        }
        else if (isAWin(HUMAN))
        {
            return HUMAN_WIN;
        }
        else if (boardIsFull())
        {
            return DRAW;
        }
        return UNCLEAR;
    }

    /**
     * Shows the board and the current situation of the game
     *
     * @param the
     *            board string.
     */
    public String toString()
    {
        return "";
    }

    /**
     * Gameover checks if someone lost the game.
     *
     * @return position boolean.
     */
    public boolean gameOver()
    {
        this.position = positionValue();
        return this.position != UNCLEAR;
    }

    /**
     * Checks to see who is the winner
     */
    public String winner()
    {
        if (this.position == COMPUTER_WIN)
            return "computer";
        else if (this.position == HUMAN_WIN)
            return "human";
        else
            return "nobody";
    }

	@Override
	public Stack<Integer> willBeReplaced(int move, int side) {
		Stack<Integer> wbr = new Stack<Integer>();
		wbr.push(move);
		return wbr;
	}

	@Override
	public boolean moveOk(int move, int side) {
		return (move >= 0 && move < rows * columns && board[move/columns][move%columns] == EMPTY );
	}
}